//***************************************************************
//  validform.js - validates the text and textarea input 
//  elements for an HTML form. 

//***************************************************************
//	Originally adapted from O'Reilly's 'Javascript: The Definitive 
//	Guide' and maintained from there.  Pass a form object to the 
//	validate() function and it will iterate the elements list and 
//	validate any text / hidden / password fields on the form.  
//	Fields are assigned specific attributes beform the call to 
//	perform specific edits.  Default attribute field definition
//	is a required text field.  See list of supported attributes
//	below for defaults and overrides.
//
//	David Connell   -  10/27/99
//
//***************************************************************
//	Maintenance Logs
//***************************************************************
//  (dkc) 10/27/1999
//			- Modified to report first error and return false.
//
//			- Added properties for invalid (not allowed) fields
//			  and positive numeric fields.		
//
//  (dkc) 03/23/2000
//			- Incorporated the date validation routines
//			  (source adapted from DateValidation.js)
//
//			- Added optional range checking for date fields
//
//  (dkc) 11/03/2003
//			- Added utility functions to retrieve select values
//			- Added time format checking
//
//	(dkc) 07/02/2004
//			- removed seconds from time format for SSI app...
//
//  (dkc) 01/02/2012
//          - Wrapped in MooTools Class structure.
//
//----------------------------------------------------------------
//	Supported Field Attributes  
//
//		optional		Field may be empty ( boolean )
//		invalid		Field must be empty	( boolean )
//		numeric		Field must be a number ( boolean )	
//		positive		Numeric field must be >= 0 ( boolean )
//		min			Numeric field lower range boundry ( numeric )
//		max			Numeric field upper range boundry ( numeric )
//		minDate		Date field lower range boundry ( MDYDate )
//		maxDate		Date field upper range boundry ( MDYDate )

//		-- Format Edits  --
//		isMDYDate	Field must be a date in m/d/yyyy format	
//    isTime		Field must be valid time element in hh:mm
//    isMilTime	Field must be valid time element  in hh:mm[:ss]  (24 hour clock)
//		isPhone		Requires 10 numerics and allows phone format chars
//		isEMail		Proper format for internet email
//		isZip			Checks for standard or Zip+4 formats
//		isState	   Validated against static variable of 2 char 
//						state abbreviations 
//		isSSN			Checks for Social Sec # format
//
//		Note: using a range and a type is redundant, if a range is 
//			  specified that range type is assumed.  I.e.  if you 
//			  specify a minDate you don't have to specify isMDYDate
//			  as true also.
//				
//----------------------------------------------------------------
var unitedStatesAbbreviations = "AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|PR|RI|SC|SD|TN|TX|VI|UT|VT|VA|WA|WV|WI|WY";
var mailStates = unitedStatesAbbreviations.split("|"); 

var FormValidator = new Class({
	
	/**
	 * Constructor
	 */
	initialize: function(frm) {
	    this.form = frm;
	},
	
	//----------------------------------------------------------------
	//  getSelectValue()
	//  retrieve the value of the selected option from
	//  a select tag.
	getSelectValue: function(form,fldName){
		// get the field from the form
		var fld = form.elements[fldName];
		var rv = -1;
		
		// if the field was valid and it was a selection,
		if (fld && fld.options)
		{
			// get the value of the selected option
			var ndx = fld.selectedIndex;
			rv = fld.options[ndx].value;
		}
		
		// return results
		return rv;
	},

	
	//----------------------------------------------------------------
	// isBlank()
	//	 A utility function that returns true if a string contains only 
	//   whitespace characters.function isblank(s)
	isBlank: function(s){
	    for(var i = 0; i < s.length; i++) {
	        var c = s.charAt(i);
	        if ((c != ' ') && (c != '\n') && (c != '\t')) return false;
	    }
	    return true;
	},

	//----------------------------------------------------------------------
	// parseDate ( )
	//   extract the date string from the value member
	//   and parses it into it's 'm,d & y' components.  Can be 
	//	 used directly or as a helper function for validDate.
	//
	// Return:
	//		True if successfully parsed, false if not.
	//
	parseDate: function( dtObj ){
		// date parsers
		var slashpos1, slashpos2, lastpos;
	
		// the date string
		var date = dtObj . value;		
		
		// parse month
		if (date.substring(1,2) != "/") {
			if (date.substring(0,1) < "0" || date.substring(0,1) > "9")
				return false;
			
			if (date.substring(1,2) < "0" || date.substring(1,2) > "9")
				return false;
			
			if (date.substring(0,2) <= "0" || date.substring(0,2) > 12)
				return false;
			
			if (date.substring(2,3) != "/")
				return false;
	
			// set month member
			dtObj . month = date.substring(0,2);
			slashpos1 = 3;
		}
		else{	
			if (date.substring(0,1) < "1" || date.substring(0,1) > "9")
				return false;
			
			// set month member
			dtObj . month = date.substring(0,1);
			slashpos1 = 2;
		} 
		
		// parse day
		if (date.substring(slashpos1+1,slashpos1+2) != "/") {
			if (date.substring(slashpos1,slashpos1+1) < "0" || date.substring(slashpos1,slashpos1+1) > "9")
				return false;
			
			if (date.substring(slashpos1+1,slashpos1+2) < "0" || date.substring(slashpos1+1,slashpos1+2) > "9")
				return false;
			
			if (date.substring(slashpos1+2,slashpos1+3) != "/")
				return false;
			
			// set day member
			dtObj . day = date.substring(slashpos1, slashpos1+2);
			slashpos2 = slashpos1 + 3;
		}
		else {
			if (date.substring(slashpos1,slashpos1+1) < "1" || date.substring(slashpos1,slashpos1+1) > "9")
				return false;
			
			// set day member
			dtObj . day = date.substring(slashpos1, slashpos1+1);
			slashpos2 = slashpos1 + 2;
		} 
		
		// parse year ( 4 digits for year representation )
		lastpos = slashpos2+3;
		if ((date.substring(slashpos2,date.length)).length > 4)
			return false;
		
		for (i=(slashpos2); i<=(lastpos);i++) {
			var temp = date.substring(i,i+1);
			if (temp < "0" || temp > "9")
				return false;			
		}
	
		// set year member
		dtObj . year = date.substring(slashpos2,lastpos+1)	
		
		// test for valid day in month
		if ((dtObj . month == 1 || dtObj . month == 3 || 
		     dtObj . month == 5 || dtObj . month == 7 || 
		     dtObj . month == 8 || dtObj . month == 10 || 
		     dtObj . month == 12) && ( dtObj . day > 31 ))
				return false;

		else 	
			// 30 day months
			if (( dtObj . month != 2 ) && ( dtObj . day > 30 ))
				return false;
			
			// Feb !Leap Year
			else if (((dtObj . year % 4) != 0) && (dtObj . day > 28))
				return false;
	
			// Feb Leap Year
			else if (dtObj . day > 29)
				return false;				
			
	
		// build hash value for comparisons
		dtObj . nVal = ( dtObj . year * 10000 ) + ( dtObj . month * 100 ) + dtObj . day
	
		// all done
		return true;
	},

	//----------------------------------------------------------------------
	// validTime()
	//   parses value for 2-3 elements within proper ranges
	//	 
	validTime: function( e ){
		var hr = -1;
		var min = -1;
		var msg = "";
		err = 0;
		
		var mxHour = (e.isMilTime ? 23 : 12);
		var  mnHour = (e.isMilTime ? 0 : 1);
		var vals = e.value.split(":");
		
		// checks by length
		if ( vals.length == 2) {
			hr = vals[0];
			min = vals[1];
		
			// check agains constraints
			if ( (hr > mxHour ) ||
			     (hr < mnHour) ||
		    	 (min < 0) ||
		     	(min > 59) )
		     		
		     		err = 1;
		}
		else
			err = 1;
			
		if (err == 1)     
		     msg = "Invalid Time Format (hh:mm)";
		     
		return msg;	     
	},
	
	//----------------------------------------------------------------------
	// validDate ( )
	//   calls parseDate() to extract and parse the string dateand then 
	//	 does any specified range checks.
	//
	// Return:
	//	 Error message suitable for validform() or null if all OK.
	//
	validDate: function( dtObj ) {
		// attempt to parse the date
		if ( !parseDate( dtObj ) )
			return ( "Invalid date format for " + dtObj . name + " ( Use 'mm/dd/yyyy' ) ." );
		
		// range testing (min)	
		if ( dtObj . minDate ){	
			// we need another date obj
			var dtMin = new Object();
			
			// extract it from original
			dtMin . value = dtObj . minDate
			
			// is it a good date
			if ( !parseDate( dtMin ) )
				return ( "Invalid Minimum Date Format ( " + dtObj . name + " )." );
	
			// do the test
			if ( dtMin	. nVal > dtObj . nVal )
				return ( dtObj . name + " must be greater than " + dtObj . minDate + "." );
		}		
	
		// range testing ( max )	
		if ( dtObj . maxDate ){	
			// we need another date obj
			var dtMax = new Object();
			
			// extract it from original
			dtMax . value = dtObj . maxDate
			
			// is it a good date
			if ( !parseDate( dtMax ) )
				return ( "Invalid Minimum Date Format ( " + dtObj . name + " )." );
	
			// do the test
			if ( dtMax	. nVal < dtObj . nVal )
				return ( dtObj . name + " must be less than " + dtObj . maxDate + "." );
		}		
		
		// if we get here, return empty as OK
		return '';
	},	

	//--------------------------------------------------------------------------	
	// This is the function that performs form verification. It should be invoked
	// (directly or otherwise) from the onSubmit() event handler of the form.
	// The handler should return whatever value this function returns.
	run: function() {
		var f = this.form;
		var msg = '';    
		var rv = true;
	    var errFld = null;
	    
		// Loop through the elements of the form, looking for all 
		// text and textarea elements.  If found, begin validations.
	    // If an error is found, a message will be displayed and the
	   	// function will exit with a false return code.    
		for(var i = 0; i < f.elements.length; i++){
			var e = f.elements[i];        
			var mtFlg = 0;        
			var numFlg = 0;
			
	        	// first determine if it is a text based input         
			if ((e.type == "text") || (e.type == "textarea") || e.type == "password"){
				var name = e.id.replace(/_/g," ");			
				
				// does the field contain any data
				mtFlg = ((e.value == null) || (e.value == "") || this.isBlank(e.value)) ? true : false;
	
				// empty field			
				if (mtFlg){			
					// required field??				
					if (!e.optional && !e.invalid) 
						msg = this.appendMsg(msg,"Please enter a value for " + name + ".");
				}
				
				// non empty field
				else {
					// dissallowed field?? (conditioned before call)
					if ( e.invalid ) 
						msg = this.appendMsg(msg,"Entry for " + name + " is not allowed with current options.");
					
					// Now check numeric validations.
					if (e.numeric || e.min || e.max || e.positive){	
						// valid number edit
						if (isNaN(e.value)) 
							msg = this.appendMsg(msg,"The field " + name + " must be a number");
						else {
							// valid number range edits
							var v = parseFloat(e.value);
						    
							// lower range check 						
							if ((e.min != null) && (v < e.min))
								msg = this.appendMsg(msg,"The field " + name + " must be greater than " + e.min);
							// upper range check
							if ((e.max != null) && (v > e.max))
								msg = this.appendMsg(msg,"The field " + name + " must be less than " + e.max);
							// non-negative edit
							if ( e.positive  &&  ( v < 0.0 ) )
								msg = this.appendMsg(msg,"The field " + name + " cannot be negative.");
						}
					}
					 	
					// location oriented edits
					// test for zip / zip+4
					if (e.isZip){
						// start with length
						x = e.value.length;
						
						// check for zip+4 format
						if (x == 10){
							// split zip  / +4
							a = e.value.split('-');
							
							// check it out
							if ((a[0].len != 5) || isNaN(a[1]) || isNaN(a[2]) )
								msg = this.appendMsg(msg,"Invalid Zip Code Format in " + name + ". [ xxxxx or xxxxx-xxxx ]");
						}
						// test traditional zip format
						else if ((x != 5) || isNaN(e.value))
							msg = this.appendMsg(msg,"Invalid Zip Code Format in " + name + ". [ xxxxx or xxxxx-xxxx ]");
					}
					
					if (e.isState){
						// format to fit the variable pattern
						found = 0;
						for(ab in mailStates)
							if(e.value.toUpperCase() == mailStates[ab])
								found ++;
								
						if (found == 0)
							msg = this.appendMsg(msg,"Invalid State Abbreviation: " + name);
					}				
					
					// test phone numbers for 10 numerics and 
					// allowed punctuation				
					if (e.isPhone) {				
						// we're looking for 10
						cnt = 0;
						
						// get entry data
						txt = e.value;
						
						// test each character
						for(x=0;x<txt.length;x++) {
							// extract char
							tst = txt.substr(x,1);
							
							// and test it form numerics
							if ( (tst >= '0') && (tst <= '9') )
								// bump counter
								cnt++;							
							else
								// test from allowed punctuation
								if ( '()-. '.indexOf(tst) == -1){
									// set flags and exit condition
									cnt = -1;
									x = txt.length;
								}
						}		
						
						// check for any right numerics count
						if (cnt != 10)
							// oops...
							msg = this.appendMsg(msg,"Invalid Phone Number Format in " + name + ". [ (xxx) xxx-xxxx ]");
					}
					
					// test email formats
					if (e.isEmail){
						// assume good result
						rv = 1;
						
						// split domain from user name
						p1 = e.value.split("@");
						
						// and check it
						if (p1.length == 2) {
							// split the domain from top domain
							p2 = p1[1].split('.');
						
							// and check it
							if (p2.length < 2)
								// invalid domain
								rv = 0;
						}		
						else
							// invalid format
						  	rv = 0;
						
						if (rv == 0)
							msg = this.appendMsg(msg,"Invalid Email Format. [ xxxx@xxxxx.xxx ]");
					}
					
					if (e.isSSN){
						pts = e.value.split('-');
						if ((pts.length != 3) || (pts[0].length != 3) || 
						    (pts[1].length != 2) || (pts[2].length != 4))
							msg = this.appendMsg(msg,"Invalid SSN Format. [ xxx-xx-xxxx ]");
					}			
					
					// Date checks (All dates expected in mm/dd/yyyy format)
					if ( e.isMDYDate || e.minDate || e.maxDate ) {
						m = this.validDate(e);
						if (m)
							msg = this.appendMessage(msg,m);
					}
					
					// Time checks 					
					if ( e.isTime || e.isMilTime ) {
						m = this.validTime( e );
						if (m)
							msg = this.appendMessage(msg,m);
					}
				}

				//---------------------------
				// evaluate the situation
				if ((msg != null) && (msg.length > 0))
					errFld = errFld ? errFld : e;			
			}
		}
	
		// error trap
		if(errFld) {
			// show error message
			this.validationErrorMessage(msg);
	
			// set focus back to the field
			errFld.focus();
	
			// and return failure flag
			rv = false;
		}	
			
	    // all well that ends well    
		return rv;
	},
	
	/**
	 * Message aggrigator
	 */
	appendMsg: function(orig, add){
		if( orig != '')
			orig += '\n\n';		
		return orig  + add;
	},

	/**
	 * Error Notification for user.
	 */
	validationErrorMessage: function( msg){
		err_msg  = "______________________________________________________\n\n";
		err_msg += "Please correct error(s) below and re-submit the form.\n";
		err_msg += "______________________________________________________\n\n";
		err_msg += msg;	
	
		// alert to user...
		alert(err_msg);
	
		return false;
	}    
});

